package com.jirong.shop.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jirong.shop.config.AccessLimit;
import com.jirong.shop.eException.GlobalException;
import com.jirong.shop.pojo.SeckillMessage;
import com.jirong.shop.pojo.TOrder;
import com.jirong.shop.pojo.TSeckillOrder;
import com.jirong.shop.pojo.TUser;
import com.jirong.shop.rabbitMQ.MQSender;
import com.jirong.shop.service.TGoodsService;
import com.jirong.shop.service.TOrderService;
import com.jirong.shop.service.TSeckillOrderService;
import com.jirong.shop.utils.JsonUtil;
import com.jirong.shop.vo.GoodsVo;
import com.jirong.shop.vo.RespBean;
import com.jirong.shop.vo.RespBeanEnum;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Controller
@Slf4j
@RequestMapping("/seckill")
public class SkillDemoController implements InitializingBean {
    @Autowired
    private TGoodsService goodsService;
    @Autowired
    private TSeckillOrderService seckillOrderService;
    @Autowired
    private TOrderService orderService;
    @Autowired
    @Qualifier("redisTemplate1")
    private RedisTemplate redisTemplate;
    @Autowired
    private MQSender mqSender;
    private Map<Long, Boolean> EmptyStockMap = new HashMap<>();

    /*
    缓存页面先
     */
    @RequestMapping("/doSeckill")
    public String doSeckill(Model model, TUser user, Long goodsId) {
        if (user == null) {
            return "login";
        }
        model.addAttribute("user", user);
//        判断库存,从
        GoodsVo goods = goodsService.findGoodsVoByGoodsId(goodsId);
        if (goods.getStockCount() < 1) {
            model.addAttribute("errmsg", RespBeanEnum.EMPTY_STOCK.getMessage());
            return "seckillFail";
        }
//      避免重复抢购
//       根据商品id和用户id判断是否再次拿到了数据
        QueryWrapper<TSeckillOrder> wrapper = new QueryWrapper<>();
        QueryWrapper<TSeckillOrder> eq = wrapper.eq("user_id", user.getId())
                .eq("goods_id", goodsId);
        TSeckillOrder one = seckillOrderService.getOne(eq);
        if (one != null) {
            model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());
            return "seckillFail";
        }
//      下订单
        TOrder order = orderService.secKill(user, goods);
        model.addAttribute("order", order);
//        可以直接携带商品
        model.addAttribute("goods", goods);
        return "orderDetail";
    }

    /*
    秒杀页面接口，走那个对应的静态页面,优化接口第三次用轮询秒杀，第二次已经保存过了
     */
    @RequestMapping(value = "/{path}/doSeckill1", method = RequestMethod.POST)
    @ResponseBody
    public RespBean doSeckill(TUser user, Long goodsId, @PathVariable String path) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
//        验证秒杀接口
        boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        //判断是否重复抢购
//        从redis中来拿
        String seckillOrderJson = (String) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
        if (!StringUtils.isEmpty(seckillOrderJson)) {
            return RespBean.error(RespBeanEnum.REPEATE_ERROR);
        }
        //内存标记,减少Redis访问
        if (EmptyStockMap.get(goodsId)) {
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        //预减库存这个具有原子性
        Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
        if (stock < 0) {
            EmptyStockMap.put(goodsId, true);
            valueOperations.increment("seckillGoods:" + goodsId);
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
//        下订单了，进入消息队列
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
//        将对象转为json进行网络传输，fackJson,fastJson，Druid自带的转化也行
        mqSender.sendsecKillMessage(JsonUtil.object2JsonStr(seckillMessage));
        return RespBean.success(0);
    }

    /*`
    ````````系统初始化，把商品库存数量加载到Redis,不用每次去数据库查，高并发情况
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> list = goodsService.findGoodsVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        for (GoodsVo goodsVo : list) {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
//            标记存入过的商品,有库存进来当然先不标记
            EmptyStockMap.put(goodsVo.getId(), false);
        }
    }

    //    轮询查结果
    @RequestMapping(value = "/result", method = RequestMethod.GET)
    @ResponseBody
    public RespBean getResult(TUser user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        Long orderId = seckillOrderService.getResult(user, goodsId);
        return RespBean.success(orderId);
    }

    /*
    秒杀接口隐藏防止泄露接口造成过早损失
    Error resolving template [seckill/path], template might not exist or might not be accessible by any of the configured Template Resolvers
    要加@ResponseBody
     */
    //    自定义注解限流（计数器算法，漏桶，令牌桶等）/5秒内最大五次）
    @RequestMapping(value = "/path", method = RequestMethod.GET)
    @ResponseBody
    @AccessLimit(second = 5, maxCount = 5, needLogin=true)
    public RespBean getPath(TUser user, Long goodsId, String captcha, HttpServletRequest request) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
//        ValueOperations valueOperations = redisTemplate.opsForValue();
//        //限制访问次数，5秒内访问5次
//        String uri = request.getRequestURI();
//        Integer count = (Integer) valueOperations.get(uri + ":" + user.getId());
//        if (count==null){
//            valueOperations.set(uri + ":" + user.getId(),1,5,TimeUnit.SECONDS);
//        }else if (count<5){
//            valueOperations.increment(uri + ":" + user.getId());
//        }else {
//            return RespBean.error(RespBeanEnum.ACCESS_LIMIT_REACHED);
//        }
//        秒杀路径时验证验证码
        boolean check = orderService.checkCaptcha(user, goodsId, captcha);
        if (!check){
            return RespBean.error(RespBeanEnum.ERROR_CAPTCHA);
        }
//        下订单查路径
        String str = orderService.createPath(user, goodsId);
        return RespBean.success(str);
    }

    /*
    图片验证码
     */
    @RequestMapping(value = "/captcha", method = RequestMethod.GET)
    public void captcha(TUser user, Long goodsId, HttpServletResponse response) {
        if (null == user || goodsId < 0) {
            throw new GlobalException(RespBeanEnum.REQUEST_ILLEGAL);
        }
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        //生成验证码，将结果放入redis
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
//        /captcha.text()
        redisTemplate.opsForValue().set("captcha:"+user.getId()+":"+goodsId,captcha.text(),120, TimeUnit.SECONDS);
//        captcha.out(outputStream);  // 输出验证码
        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            log.error("验证码生成失败",e.getMessage());
        }
    }
}
